/**
 *  Version:     @(#)aio_base.c    0.2.11 18/06/2007
 *
 *  Authors:     Hailong Xia <hlxxxx@gmail.com> 
 *               Jinyuan Hei <heijinyuan@htomail.com>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty 
 *  of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See 
 *  the GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General
 *  Public License along with this library; if not, write to the 
 *  Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
 *  Boston, MA 02111-1307  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#ifdef _WIN32
# include <winsock2.h>
#else 
# include <sys/socket.h>
# include <netinet/in.h>
#endif

#include "aio_slot.h"
#include "aio_log.h"
#include "aio_base.h"

static void
aio_ontimer(int fd, short events, void *arg)
{
        struct aio_timer *at;

        at = (struct aio_timer *)arg;
        assert(at);

        aio_evtimer_add(&at->ev, &at->tv);
        if (at->fn) (at->fn)(at, at->arg);
}

struct aio_base *
aio_create(AIO_EVBASE *eb, int size)
{
        struct aio_base *ab;

#if 0
        WSADATA wsaData;
        WORD version = MAKEWORD(2, 0);
        WSAStartup(version, &wsaData);
#endif

        ab = (struct aio_base *)calloc(1, sizeof(struct aio_base));
        assert(ab);

        ab->refcnt = 1; /* init reference count for this new aio base object */
        if (eb == NULL) {
                ab->eb = aio_evbase_create(size);
                assert(ab->eb);
        } else {
                ab->eb = eb;
        }

        FREELIST_INIT(&ab->buf_head, AIO_BUF_LISTSIZE); 
        LIST_INIT(&ab->slot_head);
        LIST_INIT(&ab->timer_head);

        log_info("AIO initialize succeed.\n");
        return ab;
}

void
aio_destroy(struct aio_base *ab, int cleanevbase)
{
        struct aio_slot *slot, *slot1;
        struct aio_slotnode *sh;
        struct aio_timer *tm;
        struct aio_timenode *th;
        void *bufnode;

        if (!ab) return;
 
        if (--ab->refcnt > 0) {
                debugp("Reduce a reference count for aio base obj.\n");
                return;
        }

        sh = &ab->slot_head;

        for (slot = LIST_FIRST(sh); slot != LIST_END(sh);) {
                slot1 = LIST_NEXT(slot, entries);
                slot->refcnt = 1; /* clean reference count */
                aio_slot_close(slot, 1);
                slot = slot1;
        }

        th = &ab->timer_head;

        while(tm = LIST_FIRST(th)) {
                aio_evtimer_del(&tm->ev);
                free(tm);
        }

        if (cleanevbase) {
                aio_evbase_destroy(ab->eb);
#if 0
                WSACleanup();
#endif
        }

        while (bufnode = FREELIST_POP(&ab->buf_head)) {
                free(bufnode);
        }

        free(ab);
}

struct aio_buf*  
aio_buf_alloc(struct aio_base *ab)
{
        struct aio_buf *buf;

        assert(ab);

        buf = (struct aio_buf *)FREELIST_POP(&ab->buf_head);
        if (buf == NULL) {
                buf = (struct aio_buf *)malloc(sizeof(struct aio_buf));
                assert(buf);
        }

        buf->head = &ab->buf_head;
        buf->offset = buf->length = 0;
        return buf;
}

void 
aio_buf_free(struct aio_buf *buf)
{
        if (buf && FREELIST_PUSH(buf->head, buf) == 0) {
                free (buf);
        }
}

struct aio_timer *
aio_timer_set(struct aio_base *ab, unsigned msec,
        void (*fn)(struct aio_timer *at, void *), void *arg)
{
        struct aio_timer *at;

        assert(ab);
        assert(ab->eb);

        at = (struct aio_timer *)calloc(1, sizeof(struct aio_timer));
        assert(at);

        at->fn = fn;
        at->arg = arg;

        aio_evtimer_set(&at->ev, aio_ontimer, at);
        aio_evbase_set(&at->ev, ab->eb);

        at->tv.tv_sec = msec / 1000;
        at->tv.tv_usec = (msec % 1000) * 1000;
        aio_evtimer_add(&at->ev, &at->tv);

        LIST_INSERT_HEAD(&ab->timer_head, at, entries);
        return at;
}

void
aio_timer_cancel(struct aio_timer *at)
{
        if (at == NULL) return;

        LIST_REMOVE(at, entries);
        aio_evtimer_del(&at->ev);
        free(at);
}

int 
aio_loop(struct aio_base* ab, unsigned msec)
{
        assert(ab);
        assert(ab->eb);

        if (msec > 0) {
                struct timeval tv;
                tv.tv_sec = msec / 1000;
                tv.tv_usec = (msec % 1000) * 1000;
                if (aio_event_shutdown(ab->eb, &tv) < 0) {
                        debugp("Failed to call aio_event_shutdown.\n");
                        return -1;
                }
        }

        return aio_event_dispatch(ab->eb);
}

